/*
 * Copyright (c) 2022-2024 KCloud-Platform-IoT Author or Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.laokou.common.core.utils;

import java.util.Random;

/**
 * <p>
 * Operations for random <code>String</code>s.
 * </p>
 * <p>
 * Currently <em>private high surrogate</em> characters are ignored. These are unicode
 * characters that fall between the values 56192 (db80) and 56319 (dbff) as we don't know
 * how to handle them. High and low surrogates are correctly dealt with - that is if a
 * high surrogate is randomly chosen, 55296 (d800) to 56191 (db7f) then it is followed by
 * a low surrogate. If a low surrogate is chosen, 56320 (dc00) to 57343 (dfff) then it is
 * placed after a randomly chosen high surrogate.
 * </p>
 *
 * <p>
 * #ThreadSafe#
 * </p>
 *
 * @author Apache Software Foundation
 * @author <a href="mailto:steven@caswell.name">Steven Caswell</a>
 * @author Gary Gregory
 * @author Phil Steitz
 * @version $Id: RandomStringUtils.java 1056988 2011-01-09 17:58:53Z niallp $
 * @since 1.0
 */
public final class RandomStringUtil {

	/**
	 * <p>
	 * Random object used by random method. This has to be not local to the random method
	 * so as to not return the same value in the same millisecond.
	 * </p>
	 */
	private static final Random RANDOM = new Random();

	/**
	 * <p>
	 * <code>RandomStringUtils</code> instances should NOT be constructed in standard
	 * programming. Instead, the class should be used as
	 * <code>RandomStringUtils.random(5);</code>.
	 * </p>
	 *
	 * <p>
	 * This constructor is public to permit tools that require a JavaBean instance to
	 * operate.
	 * </p>
	 */
	public RandomStringUtil() {
		super();
	}

	// Random
	// -----------------------------------------------------------------------

	/**
	 * <p>
	 * Creates a random string whose length is the number of characters specified.
	 * </p>
	 *
	 * <p>
	 * Characters will be chosen from the set of all characters.
	 * </p>
	 * @param count the length of random string to create
	 * @return the random string
	 */
	public static String random(int count) {
		return random(count, false, false);
	}

	/**
	 * <p>
	 * Creates a random string whose length is the number of characters specified.
	 * </p>
	 *
	 * <p>
	 * Characters will be chosen from the set of characters whose ASCII value is between
	 * <code>32</code> and <code>126</code> (inclusive).
	 * </p>
	 * @param count the length of random string to create
	 * @return the random string
	 */
	public static String randomAscii(int count) {
		return random(count, 32, 127, false, false);
	}

	/**
	 * <p>
	 * Creates a random string whose length is the number of characters specified.
	 * </p>
	 *
	 * <p>
	 * Characters will be chosen from the set of alphabetic characters.
	 * </p>
	 * @param count the length of random string to create
	 * @return the random string
	 */
	public static String randomAlphabetic(int count) {
		return random(count, true, false);
	}

	/**
	 * <p>
	 * Creates a random string whose length is the number of characters specified.
	 * </p>
	 *
	 * <p>
	 * Characters will be chosen from the set of alpha-numeric characters.
	 * </p>
	 * @param count the length of random string to create
	 * @return the random string
	 */
	public static String randomAlphanumeric(int count) {
		return random(count, true, true);
	}

	/**
	 * <p>
	 * Creates a random string whose length is the number of characters specified.
	 * </p>
	 *
	 * <p>
	 * Characters will be chosen from the set of numeric characters.
	 * </p>
	 * @param count the length of random string to create
	 * @return the random string
	 */
	public static String randomNumeric(int count) {
		return random(count, false, true);
	}

	/**
	 * <p>
	 * Creates a random string whose length is the number of characters specified.
	 * </p>
	 *
	 * <p>
	 * Characters will be chosen from the set of alpha-numeric characters as indicated by
	 * the arguments.
	 * </p>
	 * @param count the length of random string to create
	 * @param letters if <code>true</code>, generated string will include alphabetic
	 * characters
	 * @param numbers if <code>true</code>, generated string will include numeric
	 * characters
	 * @return the random string
	 */
	public static String random(int count, boolean letters, boolean numbers) {
		return random(count, 0, 0, letters, numbers);
	}

	/**
	 * <p>
	 * Creates a random string whose length is the number of characters specified.
	 * </p>
	 *
	 * <p>
	 * Characters will be chosen from the set of alpha-numeric characters as indicated by
	 * the arguments.
	 * </p>
	 * @param count the length of random string to create
	 * @param start the position in set of chars to start at
	 * @param end the position in set of chars to end before
	 * @param letters if <code>true</code>, generated string will include alphabetic
	 * characters
	 * @param numbers if <code>true</code>, generated string will include numeric
	 * characters
	 * @return the random string
	 */
	public static String random(int count, int start, int end, boolean letters, boolean numbers) {
		return random(count, start, end, letters, numbers, null, RANDOM);
	}

	/**
	 * <p>
	 * Creates a random string based on a variety of options, using default source of
	 * randomness.
	 * </p>
	 *
	 * <p>
	 * This method has exactly the same semantics as
	 * {@link #random(int, int, int, boolean, boolean, char[], Random)}, but instead of
	 * using an externally supplied source of randomness, it uses the internal static
	 * {@link Random} instance.
	 * </p>
	 * @param count the length of random string to create
	 * @param start the position in set of chars to start at
	 * @param end the position in set of chars to end before
	 * @param letters only allow letters?
	 * @param numbers only allow numbers?
	 * @param chars the set of chars to choose randoms from. If <code>null</code>, then it
	 * will use the set of all chars.
	 * @return the random string
	 * @throws ArrayIndexOutOfBoundsException if there are not
	 * <code>(end - start) + 1</code> characters in the set array.
	 */
	public static String random(int count, int start, int end, boolean letters, boolean numbers, char[] chars) {
		return random(count, start, end, letters, numbers, chars, RANDOM);
	}

	/**
	 * <p>
	 * Creates a random string based on a variety of options, using supplied source of
	 * randomness.
	 * </p>
	 *
	 * <p>
	 * If start and end are both <code>0</code>, start and end are set to <code>' '</code>
	 * and <code>'z'</code>, the ASCII printable characters, will be used, unless letters
	 * and numbers are both <code>false</code>, in which case, start and end are set to
	 * <code>0</code> and <code>Integer.MAX_VALUE</code>.
	 *
	 * <p>
	 * If set is not <code>null</code>, characters between start and end are chosen.
	 * </p>
	 *
	 * <p>
	 * This method accepts a user-supplied {@link Random} instance to use as a source of
	 * randomness. By seeding a single {@link Random} instance with a fixed seed and using
	 * it for each call, the same random sequence of strings can be generated repeatedly
	 * and predictably.
	 * </p>
	 * @param count the length of random string to create
	 * @param start the position in set of chars to start at
	 * @param end the position in set of chars to end before
	 * @param letters only allow letters?
	 * @param numbers only allow numbers?
	 * @param chars the set of chars to choose randoms from. If <code>null</code>, then it
	 * will use the set of all chars.
	 * @param random a source of randomness.
	 * @return the random string
	 * @throws ArrayIndexOutOfBoundsException if there are not
	 * <code>(end - start) + 1</code> characters in the set array.
	 * @throws IllegalArgumentException if <code>count</code> &lt; 0.
	 * @since 2.0
	 */
	public static String random(int count, int start, int end, boolean letters, boolean numbers, char[] chars,
			Random random) {
		if (count == 0) {
			return "";
		}
		else if (count < 0) {
			throw new IllegalArgumentException("Requested random string length " + count + " is less than 0.");
		}
		if ((start == 0) && (end == 0)) {
			end = 'z' + 1;
			start = ' ';
			if (!letters && !numbers) {
				start = 0;
				end = Integer.MAX_VALUE;
			}
		}

		char[] buffer = new char[count];
		int gap = end - start;

		while (count-- != 0) {
			char ch;
			if (chars == null) {
				ch = (char) (random.nextInt(gap) + start);
			}
			else {
				ch = chars[random.nextInt(gap) + start];
			}
			if ((letters && Character.isLetter(ch)) || (numbers && Character.isDigit(ch)) || (!letters && !numbers)) {
				if (ch >= 56320 && ch <= 57343) {
					if (count == 0) {
						count++;
					}
					else {
						// low surrogate, insert high surrogate after putting it in
						buffer[count] = ch;
						count--;
						buffer[count] = (char) (55296 + random.nextInt(128));
					}
				}
				else if (ch >= 55296 && ch <= 56191) {
					if (count == 0) {
						count++;
					}
					else {
						// high surrogate, insert low surrogate before putting it in
						buffer[count] = (char) (56320 + random.nextInt(128));
						count--;
						buffer[count] = ch;
					}
				}
				else if (ch >= 56192 && ch <= 56319) {
					// private high surrogate, no effing clue, so skip it
					count++;
				}
				else {
					buffer[count] = ch;
				}
			}
			else {
				count++;
			}
		}
		return new String(buffer);
	}

	/**
	 * <p>
	 * Creates a random string whose length is the number of characters specified.
	 * </p>
	 *
	 * <p>
	 * Characters will be chosen from the set of characters specified.
	 * </p>
	 * @param count the length of random string to create
	 * @param chars the String containing the set of characters to use, may be null
	 * @return the random string
	 * @throws IllegalArgumentException if <code>count</code> &lt; 0.
	 */
	public static String random(int count, String chars) {
		if (chars == null) {
			return random(count, 0, 0, false, false, null, RANDOM);
		}
		return random(count, chars.toCharArray());
	}

	/**
	 * <p>
	 * Creates a random string whose length is the number of characters specified.
	 * </p>
	 *
	 * <p>
	 * Characters will be chosen from the set of characters specified.
	 * </p>
	 * @param count the length of random string to create
	 * @param chars the character array containing the set of characters to use, may be
	 * null
	 * @return the random string
	 * @throws IllegalArgumentException if <code>count</code> &lt; 0.
	 */
	public static String random(int count, char[] chars) {
		if (chars == null) {
			return random(count, 0, 0, false, false, null, RANDOM);
		}
		return random(count, 0, chars.length, false, false, chars, RANDOM);
	}

}
